//*****************************************************************************
// eventQueueTest.c
//
// Copyright 2009 by Magpie, http://www.magpieti.com
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//*****************************************************************************

#include "eventQueuePrivate.h"

typedef struct
{
  int32u eventId;
  int8u eventStatus;
  int32u startTime;
  int16u duration;
} status_t;

typedef struct
{
  int8u index;
  int32u eventId;
  int8u eventStatus;
} expectedStatus_t;

#define STATUS_ARRAY_SIZE 32
static int8u statusIndex;
static status_t statusArray[STATUS_ARRAY_SIZE];

//*****************************************************************************
// Helper functions for testing
//*****************************************************************************

void requestScheduledEvents(int32u effectiveTime, int8u count)
{
  LOG_DEBUG("requestScheduledEvents: 0x%4x %d\r\n", effectiveTime, count);
}

void initStatusArray()
{
  int8u ii;
  for (ii=0; ii<STATUS_ARRAY_SIZE; ii++)
  {
    statusArray[ii].eventId = 0;
    statusArray[ii].eventStatus = 0;
    statusArray[ii].startTime = 0;
    statusArray[ii].duration = 0;
  }
  
  statusIndex = 0;
}

void dumpStatus(int8u index)
{
  char* description;

  switch (statusArray[index].eventStatus)
  {
    case EVENT_COMMAND_RECEIVED:
      description = "Load Control Event command received";
      break;
    case EVENT_STARTED:
      description = "Event started";
      break;
    case EVENT_COMPLETED:
      description = "Event completed";
      break;
    case EVENT_SUPERSEDED:
      description = "The event has been superseded";
      break;
    case EVENT_REJECTED_EXPIRED:
      description = "Rejected - Event was received after it had expired (Current Time > Start Time + Duration)";
      break;
    case EVENT_REJECTED:
      description = "Load Control Event command Rejected";
      break;
    default:
      description = "Reserved";
  }

  LOG_DEBUG("Status for event 0x%4x: 0x%x, %d, 0x%4x, %s\r\n",
      statusArray[index].eventId, statusArray[index].eventStatus,
      statusArray[index].startTime, statusArray[index].duration,
      description);
}

void dumpAllStatus(void)
{
  int8u ii;
  for (ii=0; ii<statusIndex; ii++)
  {
    LOG_DEBUG("status[%d]: ", ii);
    dumpStatus(ii);
  }
}

void reportedStatus(int32u eventId,
                        int8u eventStatus,
                        int32u startTime,
                        int16u duration)
{
  statusArray[statusIndex].eventId = eventId;
  statusArray[statusIndex].eventStatus = eventStatus;
  statusArray[statusIndex].startTime = startTime;
  statusArray[statusIndex].duration = duration;

  dumpStatus(statusIndex);

  statusIndex++;
}

void setDefaults()
{
  initStatusArray();
}

void eventStarted(int32u eventId)
{
  LOG_DEBUG("Started: 0x%4x\r\n", eventId);
}

void eventEnded(int32u eventId, boolean adjacentToStart)
{
  if (!adjacentToStart)
  {
    LOG_DEBUG("Ended: 0x%4x\r\n", eventId);
  }
  else
  {
    LOG_DEBUG("Skipped end: 0x%4x\r\n", eventId);
  }
}

void eventTestInit()
{
  LOG_DEBUG("initializing test\r\n"); 
  initEventQueue(&eventStarted, &eventEnded,
                     &reportedStatus, &requestScheduledEvents);
  setDefaults();
}

//*****************************************************************************
// Generic insert tests, verifies events are inserted, sorted and if queue is
// full, events are dropped as expected.
//*****************************************************************************
void addTest(int8u count,
             int32u eids[],
             int32u startTimes[],
             int16u durations[],
             int8u expectedSortIndices[],
             int32u currentTime)
{
  int8u baseStatusIndex = statusIndex;
  
  int8u expectedCount = getQueuedEventCount() + count;

  int8u ii;
  for (ii=0; ii<count; ii++)
  {
    addNewEventToQueue(eids[ii],
                       startTimes[ii],
                       durations[ii],
                       currentTime + ii);
  }

  dumpAllEvents();
  LOG_DEBUG("After all additions, number of events = %d\r\n",
             getQueuedEventCount());
  
  if (expectedCount > eventQueue_SZ)
  {
    expectedCount = eventQueue_SZ;
  }

  assertEquals(expectedCount, getQueuedEventCount());

  if(expectedSortIndices != NULL)
  {
    for (ii=0; ii<expectedCount; ii++)
    {
      assertStatus(ii + baseStatusIndex, eids[ii], EVENT_COMMAND_RECEIVED);
      eventElement_t* event = getIndexedEvent(ii);
      assertEquals(eids[expectedSortIndices[ii]], event->eventId);
    }
  }
}

//*****************************************************************************
// Test insertion of duplicate event ID.
//*****************************************************************************
void test01(void)
{
  TEST("test01");

  int32u currentTime = 0x00010000;

  eventTestInit();

  addNewEventToQueue(0x10,
                     currentTime + 60,
                     1,
                     currentTime);

  addNewEventToQueue(0x10,
                    currentTime + 180,
                    1,
                    currentTime);

  assertEquals(1, getQueuedEventCount());

  eventElement_t* event = eventFromEventId(0x10);
  assertNotNull(event);

  assertEquals(1, statusIndex);
  assertStatus(0, 0x10, EVENT_COMMAND_RECEIVED);
}

//*****************************************************************************
// Test insertion of event with maximum duration
//*****************************************************************************
void test02(void)
{
  TEST("test02");

  int32u currentTime = 0x00010000;

  eventTestInit();

  addNewEventToQueue(0x10,
                     currentTime + 60,
                     1440,
                     currentTime);

  assertEquals(1, getQueuedEventCount());

  eventElement_t* event = eventFromEventId(0x10);
  assertNotNull(event);

  assertEquals(1, statusIndex);
  assertStatus(0, 0x10, EVENT_COMMAND_RECEIVED);
}

//*****************************************************************************
// Test insertion of event with invalid duration (long)
//*****************************************************************************
void test03(void)
{
  TEST("test03");

  int32u currentTime = 0x00010000;

  eventTestInit();

  addNewEventToQueue(0x10,
                     currentTime + 60,
                     1441,
                     currentTime);

  assertEquals(0, getQueuedEventCount());

  assertEquals(1, statusIndex);
  assertStatus(0, 0x10, EVENT_REJECTED);
}

//*****************************************************************************
// Test insertion of event with invalid duration (zero)
//*****************************************************************************
void test04(void)
{
  TEST("test04");

  int32u currentTime = 0x00010000;

  eventTestInit();

  addNewEventToQueue(0x10,
                     currentTime + 60,
                     0,
                     currentTime);

  assertEquals(0, getQueuedEventCount());

  assertEquals(1, statusIndex);
  assertStatus(0, 0x10, EVENT_REJECTED_EXPIRED);
}

//*****************************************************************************
// Test insertion of event with invalid start time
//*****************************************************************************
void test05(void)
{
  TEST("test05");

  int32u currentTime = 0x00010000;

  eventTestInit();

  addNewEventToQueue(0x10,
                     0xffffffff,
                     0,
                     currentTime);

  assertEquals(0, getQueuedEventCount());

  assertEquals(1, statusIndex);
  assertStatus(0, 0x10, EVENT_REJECTED);
}

//*****************************************************************************
// Test insertion of event that is in the past
//*****************************************************************************
void test06(void)
{
  TEST("test06");

  int32u currentTime = 0x00010000;

  eventTestInit();

  addNewEventToQueue(0x10,
                     currentTime - 180,
                     1,
                     currentTime);

  assertEquals(0, getQueuedEventCount());

  assertEquals(1, statusIndex);
  assertStatus(0, 0x10, EVENT_REJECTED_EXPIRED);
}

//*****************************************************************************
// Test inserting six events in time order. Ensure that that sixth event is
// dropped and the events are properly sorted.
//*****************************************************************************
void test07(void)
{
  TEST("test07");

  eventTestInit();

  int32u currentTime = 0x00010000;
  
  int32u startTimes[6] = {currentTime + 60 + (90*0),
                          currentTime + 60 + (90*1),
                          currentTime + 60 + (90*2),
                          currentTime + 60 + (90*3),
                          currentTime + 60 + (90*4),
                          currentTime + 60 + (90*5)};
  int32u eids[6] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60};
  int8u sortedIndices[5] = {0, 1, 2, 3, 4};
  int16u durations[6] = {1,1,1,1,1,1};

  addTest(6,
          eids,
          startTimes,
          durations,
          sortedIndices,
          currentTime);
}

//*****************************************************************************
// Test inserting six events in reverse time order. Ensure that that sixth
// event is dropped and the events are properly sorted.
//*****************************************************************************
void test08(void)
{
  TEST("test08");
  
  eventTestInit();

  int32u currentTime = 0x00010000;
  
  int32u startTimes[6] = {currentTime + 60 + (90*5),
                          currentTime + 60 + (90*4),
                          currentTime + 60 + (90*3),
                          currentTime + 60 + (90*2),
                          currentTime + 60 + (90*1),
                          currentTime + 60 + (90*0)};
  int32u eids[6] = {0x60, 0x50, 0x40, 0x30, 0x20, 0x10};
  int8u sortedIndices[5] = {5,4,3,2,1};
  int16u durations[6] = {1,1,1,1,1,1};
  
  addTest(6,
          eids,
          startTimes,
          durations,
          sortedIndices,
          currentTime);
}

//*****************************************************************************
// Test inserting six events in mixed time order. Ensure that that sixth
// event is dropped and the events are properly sorted.
//*****************************************************************************
void test09(void)
{
  TEST("test09");
  
  eventTestInit();

  int32u currentTime = 0x00010000;

  int32u startTimes[6] = {currentTime + 60 + (90*1),
                          currentTime + 60 + (90*0),
                          currentTime + 60 + (90*3),
                          currentTime + 60 + (90*2),
                          currentTime + 60 + (90*5),
                          currentTime + 60 + (90*4)};
  int32u eids[6] = {0x20, 0x10, 0x40, 0x30, 0x60, 0x50};
  int8u sortedIndices[5] = {1,0,3,2,5};
  int16u durations[6] = {1,1,1,1,1,1};
  
  addTest(6,
          eids,
          startTimes,
          durations,
          sortedIndices,
          currentTime);
}

//*****************************************************************************
// Iterative test. From standard six event insert configuration, test
// inserting an event that supersedes other events.
//*****************************************************************************
void test10(void)
{
  // Test values for each iteration:
  //
  // duration = 1, start time 0x100b3, starts right before event 0x20,
  //                                   so supersedes event 0x20
  // duration = 1, start time 0x100b4, starts at same time as event 0x20,
  //                                   so supersedes event 0x20
  // duration = 1, start time 0x100b5, starts right after event 0x20,
  //                                   so supersedes event 0x20
  // duration = 2, start time 0x100b3, starts right before event 0x20,
  //                                   ends right before event 0x30,
  //                                   so supersedes event 0x20
  // duration = 2, start time 0x100b4, starts at same time as event 0x20,
  //                                   ends at same time event 0x30 starts,
  //                                   so supersedes event 0x20
  // duration = 2, start time 0x100b5, starts right after event 0x20,
  //                                   ends right after event 0x30 starts, 
  //                                   supersedes events 0x20 and 0x30

  int32u testStartTimes[6] = {0x100b3, 0x100b4, 0x100b5,
                              0x100b3, 0x100b4, 0x100b5};
  int32u testDurations[6] = {1,1,1,2,2,2};
  
  int8u ii;
  for (ii=0; ii<6; ii++)
  {
    ITERATIVE_TEST("test10", ii);

    eventTestInit();

    int32u currentTime = 0x00010000;

    int32u startTimes[6] = {currentTime + 60, // 0x1003c
                            currentTime + 180, // 0x100b4
                            currentTime + 300, // 0x1012c
                            currentTime + 420, // 0x101a4
                            currentTime + 480, // 0x101e0
                            currentTime + 540}; // 0x1021c
    int32u eids[6] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60};
    int8u sortedIndices[5] = {0,1,2,3,4};
    int16u durations[6] = {1,1,1,1,1,1};
    
    addTest(6,
            eids,
            startTimes,
            durations,
            sortedIndices,
            currentTime);

    addNewEventToQueue(0x2112,
                       testStartTimes[ii],
                       testDurations[ii],
                       currentTime);     

    LOG_DEBUG("After supersede, number of events = %d\r\n",
              getQueuedEventCount());
    dumpAllEvents();

    eventElement_t* event = eventFromEventId(0x2112);
    assertNotNull(event);

    event = eventFromEventId(0x20);
    assertNull(event);

    int8u expected = 5;
    
    if (ii == 5)
    {
      // Last iteration, we drop an additional event
      expected = 4;
      
      event = eventFromEventId(0x30);
      assertNull(event);
    }

    assertEquals(expected, getQueuedEventCount());

    assertStatus(6, 0x2112, EVENT_COMMAND_RECEIVED);
    assertStatus(7, 0x20, EVENT_SUPERSEDED);
    if (ii == 5)
    {
      // Last iteration, we drop an additional event
      assertStatus(8, 0x30, EVENT_SUPERSEDED);
    }
  }
}

//*****************************************************************************
// Iterative test. From standard six event insert configuration, test
// inserting an event that supersedes the starting event, which is active.
//*****************************************************************************
void test11(void)
{
  // Test values for each iteration:
  //
  // duration = 1, start time 0x100f0, starts at same time as event 0x20 ends,
  //                                   ends at same time event 0x30 starts,
  //                                   no supersede occurs, but event 0x50
  //                                   drops off end of queue
  // duration = 2, start time 0x1003b, starts right before event 0x10,
  //                                   which is active, but the start time
  //                                   is in the past, so effective start
  //                                   time is right now. Event 0x10 is 
  //                                   superseded, effective end time is
  //                                   set to current time, effective start
  //                                   time of 0x2112 is set to current time,
  //                                   event 0x50 drops off queue.
  //                                   Actual end of 0x10 and start of 0x2112
  //                                   will not occur until next call to
  //                                   processEvents (not tested here).
  // duration = 2, start time 0x1003c, starts at same time as event 0x10,
  //                                   which is active, but the start time
  //                                   is in the past, so effective start
  //                                   time is right now. Event 0x10 is 
  //                                   superseded, effective end time is
  //                                   set to current time, effective start
  //                                   time of 0x2112 is set to current time,
  //                                   event 0x50 drops off queue.
  //                                   Actual end of 0x10 and start of 0x2112
  //                                   will not occur until next call to
  //                                   processEvents (not tested here).
  // duration = 2, start time 0x1003d, starts right after event 0x10,
  //                                   which is active, but the start time
  //                                   is in the past, so effective start
  //                                   time is right now. Event 0x10 is 
  //                                   superseded, effective end time is
  //                                   set to current time, effective start
  //                                   time of 0x2112 is set to current time.
  //                                   Actual end of 0x10 and start of 0x2112
  //                                   will not occur until next call to
  //                                   processEvents (not tested here). Since
  //                                   0x2112 overlaps 0x20, 0x20 is superseded
  //                                  immediately and dropped from the queue.
  // duration = 1, start time 0x10040, starts in the middle of event 0x10,
  //                                   which is active. The start time
  //                                   is in the future, so effective start
  //                                   time is here yet. Event 0x10 is 
  //                                   superseded, effective end time of
  //                                   0x10 is set to start time of 0x2112.
  //                                   Event 0x50 is dropped from queue
  //

  int32u testStartTimes[5] = {0x100f0, 0x1003b, 0x1003c, 0x1003d, 0x10040};
  int32u testDurations[5] = {1,2,2,2,1};
  
  int32u droppedEid1[5] = {0x50, 0x50, 0x50, 0x20, 0x50};
  int32u modifiedEndTime[5] = {0x10078, 0x1003e, 0x1003e, 0x1003e, 0x10040};

  int8u ii;
  for (ii=0; ii<5; ii++)
  {
    ITERATIVE_TEST("test11", ii);

    eventTestInit();

    int32u currentTime = 0x00010000;

    int32u startTimes[6] = {currentTime + 60, // 0x1003c
                            currentTime + 180, // 0x100b4
                            currentTime + 300, // 0x1012c
                            currentTime + 420, // 0x101a4
                            currentTime + 480, // 0x101e0
                            currentTime + 540}; // 0x1021c
    int32u eids[6] = {0x10, 0x20, 0x30, 0x40, 0x50, 0x60};
    int8u sortedIndices[5] = {0,1,2,3,4};
    int16u durations[6] = {1,1,1,1,1,1};
    
    addTest(6,
            eids,
            startTimes,
            durations,
            sortedIndices,
            currentTime);

    // Simulate the passage of time and the first event starting
    currentTime = 0x00010000 + 62;
    activateCurrentEvent();

    addNewEventToQueue(0x2112,
                       testStartTimes[ii],
                       testDurations[ii],
                       currentTime);     

    LOG_DEBUG("After supersede, number of events = %d\r\n",
              getQueuedEventCount());
    dumpAllEvents();

    eventElement_t* event = eventFromEventId(0x2112);
    assertNotNull(event);

    event = eventFromEventId(droppedEid1[ii]);
    assertNull(event);

    assertEquals(5, getQueuedEventCount());

    event = eventFromEventId(0x10);
    assertNotNull(event);

    assertEquals(modifiedEndTime[ii], event->effectiveEndTime);   

    assertStatus(6, 0x2112, EVENT_COMMAND_RECEIVED);
    if (ii == 3)
    {
      // Iteration 3, we superced an additional event immediately
      assertStatus(7, 0x20, EVENT_SUPERSEDED);
    }

    dumpAllStatus();
  }
}

//*****************************************************************************
// Insert an event, wait for it to start, then insert an event that will
// supersede it in the future. This test uses processEvents to exercise
// event handling.
//*****************************************************************************
void test12(void)
{
  TEST("test12");

  eventTestInit();

  int32u currentTime = 0x00010000;

  int32u startTimes[2] = {currentTime + 60, // 0x3c
                          currentTime + 90}; // 0x5a
  int32u eids[2] = {0x10, 0x20};
  int16u durations[2] = {2,2};

  int8u ii = 0;

  addNewEventToQueue(eids[ii],
                     startTimes[ii],
                     durations[ii],
                     currentTime);

  eventElement_t* event10 = eventFromEventId(0x10);
  assertNotNull(event10);

  assertFalse(event10->adjacentToNextEvent);
  assertFalse(event10->superseded);
  assertFalse(event10->active);

  int16u jj;
  for (jj=58; jj<63; jj++)
  {
    int32u adjustedTime = currentTime + jj;
    processEvents(adjustedTime);
    
    if (jj >= 60)
    {
      assertTrue(event10->active);
    }
    else
    {
      assertFalse(event10->active);
    }
  }

  ii++;
  addNewEventToQueue(eids[ii],
                     startTimes[ii],
                     durations[ii],
                     currentTime + 64);

  eventElement_t* event20 = eventFromEventId(0x20);
  assertNotNull(event20);

  assertTrue(event10->active);
  assertTrue(event10->adjacentToNextEvent);
  assertTrue(event10->superseded);
  assertEquals(event20->startTime, event10->effectiveEndTime);

  assertFalse(event20->adjacentToNextEvent);
  assertFalse(event20->superseded);
  assertFalse(event20->active);

  for (jj=88; jj<93; jj++)
  {
    int32u adjustedTime = currentTime + jj;
    processEvents(adjustedTime);
    
    if (jj == 90)
    {
      assertTrue(event20->active);

      event10 = eventFromEventId(0x10);
      assertNull(event10);
    }
    else if (jj > 90)
    {
      assertTrue(event20->active);
    }
    else
    {
      assertFalse(event20->active);

      event10 = eventFromEventId(0x10);
      assertNotNull(event10);
    }
  }

  dumpAllEvents();

  for (jj=208; jj<213; jj++)
  {
    int32u adjustedTime = currentTime + jj;
    processEvents(adjustedTime);
    
    if (jj == 210)
    {
      event20 = eventFromEventId(0x10);
      assertNull(event20);
    }
  }

  dumpAllEvents();
  
  expectedStatus_t expectedStatus[6] = {{0, 0x10, EVENT_COMMAND_RECEIVED},
                                        {1, 0x10, EVENT_STARTED},
                                        {2, 0x20, EVENT_COMMAND_RECEIVED},
                                        {3, 0x10, EVENT_SUPERSEDED},
                                        {4, 0x20, EVENT_STARTED},
                                        {5, 0x20, EVENT_COMPLETED}};
  for (ii=0; ii<6; ii++)
  {
    assertStatus(expectedStatus[ii].index, expectedStatus[ii].eventId,
                 expectedStatus[ii].eventStatus);
  }
}

//*****************************************************************************
// End of tests, here is the test scaffolding
//*****************************************************************************

typedef void (*testFunc)(void);

testFunc testArray[] =
{
  &test01,
  &test02,
  &test03,
  &test04,
  &test05,
  &test06,
  &test07,
  &test08,
  &test09,
  &test10,
  &test11,
  &test12,
};

void testAll()
{
  int8u ii;
  for (ii=0; ii<(sizeof(testArray)/sizeof(testFunc)); ii++)
  {
    testArray[ii]();
  }
}

int main(int argc, char* argv[])
{
  static char* usageString = "usage:\r\n> a [all|<test #>]\r\n";
  static char* completeString = "Test complete, no errors\r\n";

  if (argc == 1)
  {
    testAll();    
    printf(completeString);
  }
  else if (argc == 2)
  {
    if (!strcmp(argv[1],"all"))
    {
      testAll();    
      printf(completeString);
    }
    else
    {
      int testNum = strtol(argv[1], NULL, 10);
      if ((testNum > 0) && (testNum <= (sizeof(testArray)/sizeof(testFunc))))
      {
        testArray[testNum-1]();
        printf(completeString);
      }
      else if (testNum > (sizeof(testArray)/sizeof(testFunc)))
      {
        printf("Invalid test number %d, max is %d\r\n", testNum, sizeof(testArray)/sizeof(testFunc));
      }
      else if (testNum == 0)
      {
        printf("Invalid test number %d\r\n", testNum);
      }
      else
      {
        printf(usageString);
      }
    }
  }
  else
  {
    printf(usageString);
  }
}
